﻿using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace XEnamel
{
	public static class ExpressionEx
	{
		/// <summary>
		/// Returns list of Convert expressions converting arguments to parameter types.
		/// </summary>
		public static Expression[] ConvertArguments(DynamicMetaObject[] args, ParameterInfo[] parms)
		{
			Expression[] callArgs = new Expression[args.Length];
			for (int index = 0; index < args.Length; index++)
			{
				callArgs[index] = args[index].Expression.ChangeType(parms[index].ParameterType).Convert(parms[index].ParameterType);
			}
			return callArgs;
		}

		public static Expression DynamicInvoke(Expression expr, params Expression[] args)
		{
			List<Expression> finalArgs = new List<Expression>(args);
			finalArgs.Insert(0, expr);
			return Expression.Dynamic(
				new SimpleInvokeBinder(new CallInfo(args.Length)),
				typeof(object),
				finalArgs);
		}

		public static DynamicExpression MakeBinary(ExpressionType type, Expression left, Expression right)
		{
			return Expression.Dynamic(new SimpleBinaryOperationBinder(type), typeof(object), left, right);
		}

		public static DynamicExpression MakeUnary(ExpressionType type, Expression expr)
		{
			return Expression.Dynamic(new SimpleUnaryOperationBinder(type), typeof(object), expr);
		}

		public static DynamicExpression And(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.And, left, right);
		}

		public static DynamicExpression Or(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.Or, left, right);
		}

		public static DynamicExpression Equal(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.Equal, left, right);
		}

		public static DynamicExpression NotEqual(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.NotEqual, left, right);
		}

		public static DynamicExpression GreaterThanOrEqual(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.GreaterThanOrEqual, left, right);
		}

		public static DynamicExpression LessThanOrEqual(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.LessThanOrEqual, left, right);
		}

		public static DynamicExpression LessThan(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.LessThan, left, right);
		}

		public static DynamicExpression GreaterThan(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.GreaterThan, left, right);
		}

		public static DynamicExpression Add(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.Add, left, right);
		}

		public static DynamicExpression Subtract(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.Subtract, left, right);
		}

		public static DynamicExpression Multiply(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.Multiply, left, right);
		}

		public static DynamicExpression Divide(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.Divide, left, right);
		}

		public static DynamicExpression Power(Expression left, Expression right)
		{
			return MakeBinary(ExpressionType.Power, left, right);
		}

		public static DynamicExpression Not(Expression expr)
		{
			return MakeUnary(ExpressionType.Not, expr);
		}

		public static DynamicExpression Negate(Expression expr)
		{
			return MakeUnary(ExpressionType.Negate, expr);
		}

		public static MethodCallExpression Call(Type type, string methodName, params Expression[] arguments)
		{
			return Expression.Call(type.GetMethod(methodName, arguments.Select(x => x.Type).ToArray()), arguments);
		}

		public static MethodCallExpression Call<T>(string methodName, params Expression[] arguments)
		{
			return Expression.Call(typeof(T).GetMethod(methodName, arguments.Select(x => x.Type).ToArray()), arguments);
		}

		/// <summary>
		/// Retrieve a variable from the input scope by name.
		/// </summary>
		/// <param name="scope">An expression which refers to an object of type Scope.</param>
		/// <param name="symbolName">A symbol name within the given Scope object.</param>
		public static Expression SymbolExpression(Expression scope, string symbolName)
		{
			if (scope.Type != typeof(DynamicMethod))
			{
				throw new ArgumentException("The scope expression must represent a DynamicMethod object.");
			}
			return Expression.Property(Expression.Call(scope, "GetSymbol", null, Expression.Constant(symbolName)), "Data");
		}

		/// <summary>
		/// Ensure that some sort of object type is returned by the expression.
		/// </summary>
		public static Expression EnsureObjectResult(Expression expr)
		{
			if (!expr.Type.IsValueType)
			{
				return expr;
			}
			if (expr.Type == typeof(void))
			{
				return Expression.Block(expr, Expression.Default(typeof(object)));
			}
			else
			{
				return expr.ChangeType<object>().Convert<object>();
			}
		}
	}
}